
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;


#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

#include "bocat/common/md5.hpp"
#include "cstore.hpp"

using namespace std;

#define	DEFAULT_TIMES	32

#define	N_ROUND	100000


float
getTimeDuration( ptime time_begin, ptime time_end )
{
	time_period	tp( time_begin, time_end );
	time_duration	td = tp.length();
	long	time_last_ms = td.total_milliseconds();
	float	time_last_s = time_last_ms / 1000.0f;

	return time_last_s;
}



void	test_001( const std::string& config_filename, int times, int data_size )
{
	sancab::SleepyStore	store( config_filename.c_str() );

	char*	buf = new char[ data_size ];
	memset( buf, 0, data_size );
	memset( buf, 'A', data_size - 1 );

	// test PUT
	{
		std::cerr << "testing PUT ...\n";
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		long	bytes_total = 0;
		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;
			ostringstream	os_data;     os_data << "DATA--" << setw(8) << setfill('0') << i << buf;

			string	key = os_key.str();
			string	data = os_data.str();

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			store.insert( kpin, key, data );

			bytes_total += data.size();

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				std::cerr << "  R-PUT " << ++count_round << "*" << N_ROUND << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		float	bw = bytes_total / time_last_s / 1048576.0;
		std::cerr << "  INSERT " << times << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << ", bw=" << bw << " MB/s" << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;
	}

	// test GET
	{
		std::cerr << "testing GET ...\n";
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		long	bytes_total = 0;
		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;
			ostringstream	os_data;     os_data << "DATA--" << setw(8) << setfill('0') << i << buf;

			string	key = os_key.str();
			string	data_want = os_data.str();

			string	data;

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			int rc = store.find( kpin, key, data );
			if (data.size() < 1) {
				std::cerr << "fail to get KEY: " << key << " (rc=" << rc << ")" << std::endl;
				return;
			}
			if (data != data_want) {
				std::cerr << "wrong data for KEY: " << key << std::endl;
				return;
			}

			bytes_total += data.size();

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				std::cerr << "  R-GET " << ++count_round << "*" << N_ROUND << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		float	bw = bytes_total / time_last_s / 1048576.0;
		std::cerr << "    GET " << times << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << ", bw=" << bw << " MB/s" << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;
	}


	// test ERASE
	{
		std::cerr << "testing ERASE ...\n";
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		bocat::common::MD5	m;

		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;

			string	key = os_key.str();

			m.clear();
			m.update( key.c_str(), key.size() );
			string	kpin((const char* )(m.digest()), m.size());

			store.erase( kpin, key );

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				std::cerr << "  R-ERASE " << ++count_round << "*" << N_ROUND << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		std::cerr << "  ERASE " << times << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;
	}

}


int	main(int argc, char* argv[])
{
	std::string	config_filename;
	int 	times = 0;
	int 	data_size = 0;
	bool	verbose = false;

    try {

        po::options_description desc("Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("verbose,v", "output verbose message")
			("times", po::value<int>(&times)->default_value(DEFAULT_TIMES), "number of test to execute")
			("data-size", po::value<int>(&data_size)->default_value( 32 ), "size of data chunk")
            ("config", po::value<std::string>(&config_filename)->default_value("store.ini"), "configure filename")
        ;   

        po::variables_map vm;    
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);    

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 1;
        }   

		if (vm.count("verbose")) {
			verbose = true;
		}
    }   
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }   
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
		return 2;
    }   

	
	test_001( config_filename, times, data_size );


	return	0;
}

